/* common.c
** (Copyright) sejishikong <sejishikong@263.net>
** Authors:    sejishikong <sejishikong@263.net>
**             Wei Lian <lianwei2003.student@sina.com> (rewrited the code based on first author's work, added the support for sound volumn control, the support for terminating player, the support for player arguments, and the support for stream record. rearranged the section dealing with status show)
**
**    This program is free software; you can redistribute it and/or modify
**    it under the terms of the GNU General Public License Version 2 as
**    published by the Free Software Foundation;
**
**    This program is distributed in the hope that it will be useful,
**    but WITHOUT ANY WARRANTY; without even the implied warranty of
**    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**    GNU General Public License for more details.
**
**    You should have received a copy of the GNU General Public License
**    along with this program; if not, write to the Free Software
**    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
**
*/

#include <sys/soundcard.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <qapplication.h>
#include <qtabwidget.h>
#include <qvbox.h>
#include <qhbox.h>
#include <qlistview.h>
#include <qlabel.h>
#include <qslider.h>
#include <qpushbutton.h>
#include <qlineedit.h>
#include <qhttp.h>
#include <qbuffer.h>
#include <qregexp.h>
#include <qprocess.h>
#include <qstatusbar.h>
#include <qtimer.h>
#include <qsettings.h>
#include <qthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <sys/time.h>
#include <qpalette.h>

//////////class
class Sopcast:public QTabWidget, QThread
{
  Q_OBJECT			//// 
public:
  Sopcast (QWidget * parent = 0, const char *name = 0);
   ~Sopcast ();
  virtual void run ();
private:
  int sound_read ();
  QListView *listchannel;
  QBuffer *bufchannel;
  QProcess *sop;
  QProcess *player;
  QStatusBar *statusbar;
  QHttp *http;
  QLineEdit *editplayer;
  QLineEdit *editchannelurl;
  QLineEdit *editchannelheader;
  QLineEdit *editrecorddir;
  int sound_handle;
  void killsop ();
  void killplayer ();
  void forkplayer ();
  void forksop ();
  int connect_to_server (char *, int);
  int socket_read (int, char *, int, int);

  QString channelname;
  QPushButton *buttonchannel;
  QPushButton *buttonplayer;
  QPushButton *buttonrecord;
  bool fetchingchannel;
  char outport[8];
  QLabel *labelinport;
  QLabel *labeloutport;
  QSlider *slidersound;
  bool togglestartplayer;

  private slots:		////
  void httpdone (bool);
  void fetchchannel ();
  void onbuttonplayerclicked ();
  void onbuttonstopclicked ();
  void onbuttonquitclicked ();
  void onbuttonrecordclicked ();
  void onbuttonsaveclicked ();
  void onlistitemclicked ();
  void onsopexit ();
  void parsestdout ();
  void sound_write (int);
  void timerDone ();
  void onplayerexit ();
};


Sopcast::Sopcast (QWidget * parent, const char *name):
QTabWidget (parent, name)
{
////tab1
  QVBox *tab = new QVBox (this);
  listchannel = new QListView (tab);
  listchannel->addColumn (tr ("Channel"));
  listchannel->addColumn (tr ("User"));
  listchannel->addColumn (tr ("URL"));

  QHBox *hbox = new QHBox (tab);
  buttonchannel = new QPushButton ("Channel", hbox);
  buttonchannel->setToggleButton (TRUE);
  buttonplayer = new QPushButton ("Player", hbox);
  QPushButton *buttonstop = new QPushButton ("Stop", hbox);

  if ((sound_handle = open ("/dev/mixer", O_RDWR, 0)) == -1)
    {
      perror ("/dev/mixer");
      return;
    }
  slidersound =
    new QSlider (0, 100, 10, sound_read (), QSlider::Horizontal, hbox);

  buttonrecord = new QPushButton ("Record", hbox);

  statusbar = new QStatusBar (tab);

  addTab (tab, "Sopcast");
/////tab2
  tab = new QVBox (this);
  QSettings settings;

  hbox = new QHBox (tab);
  QLabel *label;
  label = new QLabel ("Player:", hbox);
  editplayer =
    new QLineEdit (settings.
		   readEntry ("/qt-sopcast/player",
			      "mplayer -ontop -geometry 100%:100%"), hbox);

  hbox = new QHBox (tab);
  label = new QLabel ("Channels url:", hbox);
  editchannelurl =
    new QLineEdit (settings.
		   readEntry ("/qt-sopcast/channelurl",
			      "http://channel.sopcast.com/gchlxml"), hbox);

  hbox = new QHBox (tab);
  label = new QLabel ("Channels url header:", hbox);
  editchannelheader =
    new QLineEdit (settings.
		   readEntry ("/qt-sopcast/channelheader", "sop://"), hbox);

  hbox = new QHBox (tab);
  label = new QLabel ("Record directory:", hbox);
  char *record_dir;
  record_dir =
    (char *) malloc (strlen (getenv ("HOME")) + strlen ("/SOPCAST/") + 1);
  strcpy (record_dir, getenv ("HOME"));
  strcat (record_dir, "/SOPCAST/");
  editrecorddir =
    new QLineEdit (settings.
		   readEntry ("/qt-sopcast/recorddir", record_dir), hbox);
  free (record_dir);

  hbox = new QHBox (tab);
  label = new QLabel ("Inport:", hbox);
  labelinport = new QLabel (hbox);
  label = new QLabel ("Outport:", hbox);
  labeloutport = new QLabel (hbox);

  QPushButton *buttonsave = new QPushButton ("Save", tab);

  addTab (tab, "Config");
/////signal and slot  
  connect (slidersound, SIGNAL (valueChanged (int)),
	   SLOT (sound_write (int)));
  http = new QHttp ();
  connect (http, SIGNAL (done (bool)), SLOT (httpdone (bool)));
  connect (buttonchannel, SIGNAL (clicked ()), this, SLOT (fetchchannel ()));
  connect (buttonstop, SIGNAL (clicked ()), this,
	   SLOT (onbuttonstopclicked ()));
  connect (buttonplayer, SIGNAL (clicked ()), this,
	   SLOT (onbuttonplayerclicked ()));
  connect (buttonrecord, SIGNAL (clicked ()), this,
	   SLOT (onbuttonrecordclicked ()));
  connect (buttonsave, SIGNAL (clicked ()), this,
	   SLOT (onbuttonsaveclicked ()));
  connect (listchannel, SIGNAL (doubleClicked (QListViewItem *)), this,
	   SLOT (onlistitemclicked ()));
  sop = new QProcess (this);
  connect (sop, SIGNAL (readyReadStdout ()), this, SLOT (parsestdout ()));
  connect (sop, SIGNAL (processExited ()), this, SLOT (onsopexit ()));
  player = new QProcess (this);
  connect (player, SIGNAL (processExited ()), this, SLOT (onplayerexit ()));
  bufchannel = new QBuffer ();

  QTimer *timer = new QTimer (this);
  connect (timer, SIGNAL (timeout ()), this, SLOT (timerDone ()));
  timer->start (1000, FALSE);
////initialization
  fetchingchannel = FALSE;
  fetchchannel ();
  togglestartplayer = FALSE;
}


Sopcast::~Sopcast ()
{
  ::close (sound_handle);
}

void
Sopcast::timerDone ()
{
  int value = sound_read ();
  if (slidersound->value () != value)
    slidersound->setValue (value);
}

////slots
void
Sopcast::onplayerexit ()
{
  buttonplayer->setPaletteForegroundColor (Qt::black);
}


void
Sopcast::onsopexit ()
{
  statusbar->clear ();
}

void
Sopcast::onbuttonrecordclicked ()
{
  if (buttonrecord->paletteForegroundColor () == Qt::black)
    buttonrecord->setPaletteForegroundColor (Qt::red);
  else
    buttonrecord->setPaletteForegroundColor (Qt::black);
}

void
Sopcast::onbuttonsaveclicked ()
{
  QSettings settings;
  settings.writeEntry ("/qt-sopcast/player", editplayer->text ());
  settings.writeEntry ("/qt-sopcast/channelurl", editchannelurl->text ());
  settings.writeEntry ("/qt-sopcast/channelheader",
		       editchannelheader->text ());
  settings.writeEntry ("/qt-sopcast/recorddir", editrecorddir->text ());

}

void
Sopcast::onlistitemclicked ()
{
  killsop ();
  killplayer ();
  channelname = listchannel->currentItem ()->text (0);
  forksop ();
  statusbar->message (tr ("Connecting..."));
}

void
Sopcast::onbuttonplayerclicked ()
{
  killplayer ();
  forkplayer ();
}

void
Sopcast::onbuttonstopclicked ()
{
  killsop ();
  killplayer ();
  statusbar->clear ();
}

void
Sopcast::onbuttonquitclicked ()
{
  if (this->running ())
    {
      this->terminate ();
      this->wait ();
    }
  killsop ();
  killplayer ();
}

////fork new process
void
Sopcast::forksop ()
{
  int i;
  char inport[8];
  int sock;
  ///choose inport and outport number
  for (i = 8000; i < 9000; i++)
    {
      sock = connect_to_server ("127.0.0.1", i);
      if (sock == -1)
	break;
      else if (sock >= 0)
	::close (sock);
    }
  sprintf (inport, "%d", i);
  labelinport->setText (inport);

  for (i = 9000; i < 10000; i++)
    {
      sock = connect_to_server ("127.0.0.1", i);
      if (sock == -1)
	break;
      else if (sock >= 0)
	::close (sock);
    }
  sprintf (outport, "%d", i);
  labeloutport->setText (outport);
  ////    
  sop->clearArguments ();
  sop->addArgument ("sp-sc");
  sop->addArgument (listchannel->currentItem ()->text (2));
  sop->addArgument (inport);
  sop->addArgument (outport);
  sop->start ();
}

void
Sopcast::forkplayer ()
{
  player->clearArguments ();
  QStringList strlst = QStringList::split (" ", editplayer->text ());
  for (QStringList::Iterator it = strlst.begin (); it != strlst.end (); ++it)
    {
      player->addArgument (*it);
    }

  char *url;
  url = (char *) malloc (strlen ("http://localhost:") + strlen (outport) + 1);
  strcpy (url, "http://localhost:");
  strcat (url, outport);
  player->addArgument (url);
  free (url);
  buttonplayer->setPaletteForegroundColor (Qt::blue);
  player->start ();

}

////kill process
void
Sopcast::killsop ()
{
  togglestartplayer = FALSE;
  if (sop->isRunning ())
    {
//      sop->tryTerminate ();
      sop->kill ();
    }
}

void
Sopcast::killplayer ()
{
  if (player->isRunning ())
    {
      player->tryTerminate ();
//      QTimer::singleShot (500, player, SLOT (kill ()));
    }

}

///thread record
void
Sopcast::run ()
{
  struct timeval t1;
  char t1str[11];
  int n, i;
  char buffer[1024], *buf2;
  char *data_path;
  ////compose data_path
  data_path =
    (char *) malloc ((editrecorddir->text ()).length () +
		     channelname.length () + 10 + 4 + 2);
  strcpy (data_path, (editrecorddir->text ()).ascii ());
  strcat (data_path, "/");
  if (access (data_path, F_OK))
    mkdir (data_path, S_IRWXU);
  strcat (data_path, channelname.ascii ());
  gettimeofday (&t1, 0);
  sprintf (t1str, "%lu", t1.tv_sec);
  strcat (data_path, t1str);
  strcat (data_path, ".asf");
  ////connect to server
  int sockfd = connect_to_server ("127.0.0.1", atoi (outport));
  if (sockfd < 0)
    {
      free (data_path);
      buttonrecord->setPaletteForegroundColor (Qt::black);
      return;
    }
  ////open file
  FILE *fp = NULL;
  fp = fopen (data_path, "w");
  free (data_path);
  if (fp == NULL)
    {
      ::close (sockfd);
      printf ("err open file\n");
      buttonrecord->setPaletteForegroundColor (Qt::black);
      return;
    }
  ////dealing with sp-sc 0.8.1 problem
  write (sockfd, "1", strlen ("1") + 1);
  ///parsing head
  n = socket_read (sockfd, buffer, sizeof buffer, 1);
  if (n == 0 || n == -1 || n == -3)
    {
      ::close (sockfd);
      if (fp != NULL)
	fclose (fp);
      buttonrecord->setPaletteForegroundColor (Qt::black);
      return;
    }
  buf2 = buffer;
  for (i = 0; i < 2; i++)
    {
      while ((*buf2) != 10)
	{
	  n--;
	  buf2++;
	}
      n--;
      buf2++;
    }
  if (n > 0)
    fwrite (buf2, n, 1, fp);
  ///record content
  while (1)
    {
      n = socket_read (sockfd, buffer, sizeof buffer, 1);
      if (n == 0 || n == -1 || n == -3)
	{
	  ::close (sockfd);
	  if (fp != NULL)
	    fclose (fp);
	  buttonrecord->setPaletteForegroundColor (Qt::black);
	  return;
	}
      fwrite (buffer, n, 1, fp);
      if (buttonrecord->paletteForegroundColor () == Qt::black)
	break;
    }
  ::close (sockfd);
  if (fp != NULL)
    fclose (fp);
}

void
Sopcast::parsestdout ()
{
  QString str, str1, str2, threadstate;
  str = QString (sop->readStdout ());
  if (str.contains ("hook_sc") || str.contains ("downloadRate"))
    {
      //     statusbar->message (tr ("Connected"));
      if (!player->isRunning () && togglestartplayer == FALSE)
	{
	  togglestartplayer = TRUE;
	  forkplayer ();
	}
    }
  if (str.contains ("downloadRate") && str.contains ("dnSum"))
    {
      ////record
      if ((buttonrecord->paletteForegroundColor () == Qt::red)
	  && (!this->running ()))
	{
	  this->start ();
	}
      else if ((buttonrecord->paletteForegroundColor () == Qt::black)
	       && this->running ())
	{
	  this->wait ();
	}
      ////status show
      int len = str.find ("dnSum") - str.find ("downloadRate") - 13;
      if (len > 0)
	{
	  str1 = str.mid (str.find ("downloadRate") + 13, len);

	  str2 = str.mid (str.find ("dnSum") + 6, 20);
	  str2 = str2.mid (0, str2.find (QRegExp ("[^0-9]+")));
	  threadstate = this->running ()? "V " : "-> ";
	  statusbar->message (threadstate + "R=" + str1 + "S=" + str2 + " " +
			      channelname);
	}
    }
/*  if (str.contains ("spsc_cleanup"))
    {
      statusbar->message (tr ("sp-sc cleanup"));
    }*/
}

void
Sopcast::fetchchannel ()
{
  if (fetchingchannel == FALSE)
    {
      fetchingchannel = TRUE;
      buttonchannel->setOn (TRUE);
      if (!bufchannel->open (IO_ReadWrite))
	{
	  printf ("cannot create the buffer\n");
	  return;
	}

      QString str = editchannelurl->text ();
      http->setHost (str.section ("/", 2, 2));	//"www.sopcast.org");
      http->get ("/" + str.section ("/", 3), bufchannel);	//"/channel/index.php", bufchannel);
    }
  else
    {
      if (!buttonchannel->isOn ())
	buttonchannel->setOn (TRUE);
    }
}

void
Sopcast::httpdone (bool error)
{
  if (buttonchannel->isOn ())
    buttonchannel->setOn (FALSE);
  fetchingchannel = FALSE;
  ///
  bufchannel->reset ();
  QTextStream textstream (bufchannel);
  textstream.setEncoding (QTextStream::UnicodeUTF8);

  QString str;
  QString itemstr[3];

  listchannel->clear ();
  int count = 0;
  QListViewItem *myChild;
  int flag;
  while (textstream.atEnd () == 0)
    {
      str = textstream.readLine ();
      int numperline = 0;
      while (1)
	{
	  if (str.contains (editchannelheader->text ()))
	    {
	      itemstr[0] =
		str.mid (str.find ("<name cn=\"\">") +
			 strlen ("<name cn=\"\">"),
			 str.find ("</name>") - str.find ("<name cn=\"\">") -
			 strlen ("<name cn=\"\">"));
	      itemstr[1] =
		str.mid (str.find ("<user_count>") + strlen ("<user_count>"),
			 str.find ("</user_count>") -
			 str.find ("<user_count>") - strlen ("<user_count>"));
	      itemstr[2] =
		str.mid (str.find ("<item>") + strlen ("<item>"),
			 str.find ("</item>") - str.find ("<item>") -
			 strlen ("<item>"));
	      ///update str
	      str = str.mid (str.find ("</item>") + strlen ("</item>"));
	      numperline++;
	      ///adding only unduplicated rows
	      flag = 0;
	      myChild = listchannel->firstChild ();
	      while (myChild)
		{
		  if (myChild->text (2) == itemstr[2])
		    flag = 1;
		  myChild = myChild->nextSibling ();
		}
	      if (flag == 0)
		{
		  count++;
		  new QListViewItem (listchannel, itemstr[0], itemstr[1],
				     itemstr[2]);
		}
	    }
	  else
	    break;
	}
      if (numperline > 1)
	printf ("numperline=%d\n", numperline);
    }

  printf ("count=%d\n", count);
  bufchannel->close ();
  if (error)
    statusbar->message (tr ("load page error!"));
}

int
Sopcast::sound_read (void)
{
  int result;
  if (::ioctl (sound_handle, MIXER_READ (4), &result) == -1)
    {
      perror ("read_error");
      return -1;
    }
  result = ((result >> 8) + (result & 0xFF)) >> 1;
  result = (result > 0) ? result : 0;
  result = (result < 100) ? result : 100;
  return result;
}

void
Sopcast::sound_write (int leftright)
{
  leftright = (leftright < 0) ? 0 : leftright;
  leftright = (leftright > 100) ? 100 : leftright;
  leftright = (leftright << 8) | (leftright & 0xFF);
  if (::ioctl (sound_handle, MIXER_WRITE (4), &leftright) == -1)
    {
      perror ("write_error");
      return;
    }
}

int
Sopcast::connect_to_server (char *host, int portnum)
{
  int sock;
  struct sockaddr_in serv_addr;
  struct hostent *server;

  sock =::socket (AF_INET, SOCK_STREAM, 0);
  if (sock == -1)
    {
      perror ("ERROR opening socket");
      return -3;
    }
  server =::gethostbyname (host);
  if (server == NULL)
    {
      fprintf (stderr, "ERROR, no such host\n");
      return -2;
    }
  ::memset ((char *) &serv_addr, 0, sizeof (serv_addr));
  serv_addr.sin_family = AF_INET;
  ::memcpy ((char *) &serv_addr.sin_addr.s_addr,
	    (char *) server->h_addr, server->h_length);
  serv_addr.sin_port = htons (portnum);

//  int flags = fcntl (sock, F_GETFL, 0);
//  fcntl (sock, F_SETFL, flags | O_NONBLOCK);
  if (::connect
      (sock, (struct sockaddr *) &serv_addr, sizeof (serv_addr)) == -1)
    {
      ::close (sock);
      return -1;
    }
  return sock;
}

int
Sopcast::socket_read (int sock, char *buffer, int size, int timeout)
	////-2:timeout, -1:select err, -3:recv err, 0:server shutdown
{
  int ret, red = 0;
  fd_set fds;
  struct timeval tv;

  FD_ZERO (&fds);
  while (size)
    {
      if (timeout > 0)
	{
	  // Wait up to 'timeout' seconds for data on socket to be ready for read
	  FD_SET (sock, &fds);
	  tv.tv_sec = timeout;
	  tv.tv_usec = 0;
	  ret =::select (sock + 1, &fds, NULL, NULL, &tv);
	  if (ret == -1)
	    {
	      printf ("select failed\n");
	      return -1;
	    }
	  if (ret != 1)
	    {
	      return -2;
	    }
	}
      ret =::recv (sock, buffer + red, size, 0);
      if (ret == -1)
	{
	  printf ("RECV failed\n");
	  return -3;
	}
      /* Got zero bytes on blocking read.  For unix this is an 
         orderly shutdown. */
      if (ret == 0)
	{
	  printf ("recv recieved zero bytes!\n");
	  break;
	}
      red += ret;
      size -= ret;
    }
  return red;
}

int
main (int argc, char **argv)
{
  QApplication a (argc, argv);

  Sopcast w;

  QObject::connect (&a, SIGNAL (aboutToQuit ()), &w,
		    SLOT (onbuttonquitclicked ()));

  w.resize (350, 350);
  a.setMainWidget (&w);
  w.show ();
  return a.exec ();
}

#include "main.moc"
